﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using Microsoft.WowAddonStudio.Lua.RuntimeService.Common;
using Microsoft.WowAddonStudio.Lua.RuntimeService.ShellHost;
using Microsoft.WowAddonStudio.Lua.RuntimeService.ToolWindows;
using Microsoft.WowAddonStudio.Lua.RuntimeService.WowBench;
using Microsoft.WowAddonStudio.Services;

namespace Microsoft.WowAddonStudio.Lua.RuntimeService
{
	/// <summary>
	/// Represents a Lua Runtime component for WoWAddOnStudio.
	/// </summary>
	[Serializable]
	public class LuaRuntime : ISerializable, ILuaRuntime
	{
		private readonly IList<string> luaFilePaths;
		private readonly IServiceProvider serviceProvider;
		private readonly ICommandResolver commandResolver;
		private readonly ICommandResolver expressionResolver;

		/// <summary>
		/// Initializes a new instance of the <see cref="LuaRuntime"/> class.
		/// </summary>
		/// <param name="provider">The <see cref="IServiceProvider"/> parameter.</param>
		/// <param name="resolverFactory">The <see cref="IResolverFactory"/> parameter.</param>
		/// <param name="shellHostFactory">The <see cref="IShellHostFactory"/> parameter.</param>
		public LuaRuntime(IServiceProvider provider, IResolverFactory resolverFactory, IShellHostFactory shellHostFactory)
		{
			Name = GetType().Name;

			if (provider == null)
				throw new LuaRuntimeInitializationException(this, "provider");

			if (shellHostFactory == null)
				throw new LuaRuntimeInitializationException(this, "shellHostFactory");

			if (resolverFactory == null)
				throw new LuaRuntimeInitializationException(this, "resolverFactory");

			//Initialize local fields
			luaFilePaths = new List<string>();
			commandResolver = resolverFactory.CreateCommandResolver();
			expressionResolver = resolverFactory.CreateExpressionResolver();
			serviceProvider = provider;
			HostFactory = shellHostFactory;
			WowBenchPath = String.Empty;

			SetWowBenchPath();

			RuntimeController = new WoWBenchController(this);
			ToolWindowControl = new WoWBenchControl(RuntimeController);
		}


		/// <summary>
		/// Initializes a new instance of the <see cref="LuaRuntime"/> class.
		/// </summary>
		/// <param name="info">The info.</param>
		/// <param name="context">The context.</param>
		protected LuaRuntime(SerializationInfo info, StreamingContext context)
		{
		}

		/// <summary>
		/// Sets the wow bench path.
		/// </summary>
		private void SetWowBenchPath()
		{
			// Query for the settings service and get the WoWBench folder path
			var settings = serviceProvider.GetService(typeof(IAddonStudioSettings)) as IAddonStudioSettings;

			if (settings != null)
				WowBenchPath = settings.CustomWowBenchPath;

			if (string.IsNullOrEmpty(WowBenchPath))
			{
				string path = Resources.DefaultWoWBenchPath;
				if (Path.IsPathRooted(path))
					WowBenchPath = path;
				else
				{
					string appPath = Path.GetDirectoryName(
						Assembly.GetCallingAssembly().GetModules()[0].FullyQualifiedName);
					appPath = appPath.Substring(0, appPath.LastIndexOf("\\"));
					WowBenchPath = Path.Combine(appPath, path);
				}
			}
		}

		#region Properties

		/// <summary>
		/// Defines a mechanism for retrieving a service object;
		/// </summary>
		public IServiceProvider Site
		{
			get { return serviceProvider; }
		}

		/// <summary>
		/// Gets the IToolWindowControl implementation.
		/// </summary>
		public IToolWindowControl ToolWindowControl { get; set; }

		/// <summary>
		/// Gets the IWoWBenchController implementation.
		/// </summary>
		public IWoWBenchController RuntimeController { get; set; }

		/// <summary>
		/// Gets the current ShellHost for runtime.
		/// </summary>
		public IShellHost RuntimeShellHost { get; set; }

		/// <summary>
		/// Gets the path of WoWBench application.
		/// </summary>
		public string WowBenchPath { get; set; }

		/// <summary>
		/// Gets IShellHostFactory implementation.
		/// </summary>
		public IShellHostFactory HostFactory { get; set; }

		/// <summary>
		/// Gets the name of the Runtime.
		/// </summary>
		public string Name { get; private set; }

		/// <summary>
		/// Gets a value indicating whether the runtime has been disposed of.
		/// </summary>
		public bool IsDisposed { get; private set; }

		#endregion

		#region Public Functions

		/// <summary>
		/// Load the specified Lua Runtime by <paramref name="runtimeType"/> parameter.
		/// </summary>
		/// <param name="runtimeType">The <see cref="LuaRuntimeType"/> parameter.</param>
		/// <returns>True if process started successfully, otherwise false.</returns>
		public bool Load(LuaRuntimeType runtimeType)
		{
			SetWowBenchPath();

			bool result = false;
			//Check WoWBench directory
			if (!Directory.Exists(WowBenchPath))
			{
				RuntimeController.TextOutputWriter.WriteErrorText(
					String.Format(Resources.WoWBenchFolderNotFoundMessage, WowBenchPath));
			}
			else
			{
				//Check Lua interpreter executable
				if (!File.Exists(Path.Combine(WowBenchPath, ShellCommandSet.LuaPrompt.Host)))
				{
					RuntimeController.TextOutputWriter.WriteErrorText(
						String.Format(Resources.LuaExecutableNotFoundMessage, ShellCommandSet.LuaPrompt.Host));
				}
				else
				{
					try
					{
						//if ShellHost exits then dispose it
						if (RuntimeShellHost != null)
						{
							RuntimeShellHost.OnProcessExited -= OnShellProcessExited;
							RuntimeShellHost.Dispose();
						}
						//Create the new ShellHost instance
						RuntimeShellHost = CreateShellHost();
						Environment.CurrentDirectory = WowBenchPath;
						ShellCommandSet shellCommandSet = PrepareShellProcess(runtimeType);

						//Launch the specified process.
						result = LaunchShellProcess(shellCommandSet);
					}
					catch (Exception ex)
					{
						Trace.WriteLine(ex);
						throw;
					}
				}
			}
			return result;
		}

		/// <summary>
		/// Adds a lua file to the runtime.
		/// </summary>
		/// <param name="luaFilePath">The path to the lua file.</param>
		public void AddLuaFile(string luaFilePath)
		{
			if (luaFilePath == null)
				throw new ArgumentNullException("luaFilePath");
			if (!File.Exists(luaFilePath))
				throw new FileNotFoundException(String.Format(Resources.CannotFindLuaCodeFile, luaFilePath));

			luaFilePaths.Add(luaFilePath);
			string expressionContent = File.ReadAllText(luaFilePath);
			EvaluateExpression(expressionContent);
		}

		/// <summary>
		/// Removes a lua file from the runtime.
		/// </summary>
		/// <param name="luaFilePath">The path to the lua file.</param>
		public void RemoveLuaFile(string luaFilePath)
		{
			if (luaFilePath == null)
				throw new ArgumentNullException("luaFilePath");

			luaFilePaths.Remove(luaFilePath);
		}

		/// <summary>
		/// Evaluates a Lua expression.
		/// </summary>
		/// <param name="expression">The expression text.</param>
		/// <returns></returns>
		public object EvaluateExpression(string expression)
		{
			Trace.WriteLine(String.Concat("LuaRuntime.EvaluateExpression: ", expression));
			if (!string.IsNullOrEmpty(expression))
			{
				var service = (ILuaRuntimeService) serviceProvider.GetService(typeof (ILuaRuntimeService));

				if (service == null)
					throw new LuaRuntimeServiceNotFoundException(this);

				//Display WoWBench ToolWindow
				service.ShowWowBenchToolWindow();

				//Start shell if needed
				bool result = InitShellHost();

				//Set ToolWindow state by execution result
				ToolWindowControl.SetControlState(result);

				//Load the given Lua expression into emulator context
				if (result)
					RuntimeController.ExecuteCommand(expressionResolver.Resolve(expression));
			}
			return null;
		}

		#endregion

		#region Private Functions

		/// <summary>
		/// Prepare the specified ShellCommandSet for execution
		/// by the given <paramref name="runtimeType"/>.
		/// </summary>
		/// <param name="runtimeType">The <see cref="LuaRuntimeType"/> parameter.</param>
		/// <returns>The <see cref="ShellCommandSet"/> result.</returns>
		private ShellCommandSet PrepareShellProcess(LuaRuntimeType runtimeType)
		{
			ShellCommandSet shellCommandSet = ShellCommandSet.Default;
			//Select the specified ShellCommandSet by runtimeType parameter
			switch (runtimeType)
			{
				case LuaRuntimeType.WoWBench:
					{
						shellCommandSet = ShellCommandSet.WoWBenchPromptArgs;
						break;
					}
				case LuaRuntimeType.WoWBenchAddon:
					{
						shellCommandSet = ShellCommandSet.WoWBenchPromptWithAddon;
						BuildActiveProject();
						break;
					}
				case LuaRuntimeType.LuaInterpreter:
					{
						shellCommandSet = ShellCommandSet.LuaPrompt;
						break;
					}
			}
			return shellCommandSet;
		}

		/// <summary>
		/// Loads the <see cref="IShellHost"/> into the runtime context.
		/// </summary>
		private bool InitShellHost()
		{
			bool result = true;
			if (RuntimeShellHost == null)
				RuntimeShellHost = CreateShellHost();

			//Re-load shell if its state is closed
			if (RuntimeShellHost.ShellState == ShellHostState.Closed)
				result = Load(LuaRuntimeType.WoWBenchAddon);

			return result;
		}

		/// <summary>
		/// Factory method for creating <see cref="IShellHost"/> implementation.
		/// </summary>
		/// <returns>The <see cref="IShellHost"/> implementation.</returns>
		private IShellHost CreateShellHost()
		{
			IShellHost shellHost = HostFactory.CreateShellHost();
			shellHost.OnProcessExited += OnShellProcessExited;
			return shellHost;
		}

		/// <summary>
		/// Fires when the shell process exited.
		/// </summary>
		/// <param name="sender">The <see cref="IShellHost"/> instance.</param>
		/// <param name="e">The <see cref="ProcessExitedEventArgs"/> parameter.</param>
		private void OnShellProcessExited(object sender, ProcessExitedEventArgs e)
		{
			expressionResolver.Reset();

			SendMessage(Resources.WoWBenchTerminatedMessage, true);

			RuntimeController.Reset();

			ToolWindowControl.ReadOnly = true;
		}

		/// <summary>
		/// Sends the given message to the output.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <param name="error">Indicates that the given message is an error message.</param>
		private void SendMessage(string message, bool error)
		{
			if (message != null)
				RuntimeController.SendMessage(message, error);
		}

		/// <summary>
		/// Launch the given <see cref="ShellCommandSet"/> set in shell host.
		/// </summary>
		/// <param name="shellCommandSet">The <see cref="ShellCommandSet"/> parameter.</param>
		private bool LaunchShellProcess(ShellCommandSet shellCommandSet)
		{
			if (RuntimeShellHost == null)
				throw new RuntimeShellHostNotFoundException(this);

			string host = commandResolver.Resolve(shellCommandSet.Host);
			string args = commandResolver.Resolve(shellCommandSet.Argument);

			Trace.WriteLine(String.Concat("Launching command set: ", host, " ", args));
			//Launch the specified process.
			bool result = RuntimeShellHost.LaunchShellProcess(RuntimeController.TextOutputWriter,
			                                                  Environment.CurrentDirectory, host, args);
			//Launch additional commands if presents.
			if (shellCommandSet.CommandSet != null)
			{
				foreach (string command in shellCommandSet.CommandSet)
				{
					RuntimeShellHost.ExecuteCommand(commandResolver.Resolve(command));
				}
			}
			return result;
		}

		/// <summary>
		/// Invokes MSBuild using the default configuration and does without logging 
		/// on the output window pane.
		/// </summary>
		private void BuildActiveProject()
		{
			try
			{
				DTE2 dte = (DTE2) serviceProvider.GetService(typeof (DTE));
				object activeSolutionProjects = dte.ActiveSolutionProjects;

				if (dte.Solution != null && activeSolutionProjects != null)
				{
					object[] objects = (object[]) activeSolutionProjects;
					Project[] projects = Array.ConvertAll(objects, project => (Project) project);

                    // TODO: Test this
					if (projects.Length > 0)
					{
					    string activeConfiguration = dte.Solution.SolutionBuild.ActiveConfiguration.Name;

                        //RuntimeController.TextOutputWriter.WriteInfoText(Resources.BeforeBuildMessage);
                        dte.Solution.SolutionBuild.BuildProject(activeConfiguration, projects[0].UniqueName, true);
                        //RuntimeController.TextOutputWriter.WriteInfoText(String.Format(Resources.AfterBuildMessage, msBuildResult) + Environment.NewLine);
					}
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
				RuntimeController.TextOutputWriter.WriteErrorText(String.Format("Build failed: {0}", ex.Message));
			}
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Releases unmanaged resources and performs other cleanup operations before the
		/// <see cref="LuaRuntime"/> is reclaimed by garbage collection.
		/// </summary>
		~LuaRuntime()
		{
			Dispose(false);
		}


		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing,
		/// or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}


		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			try
			{
				if (expressionResolver != null)
					expressionResolver.Reset();
				if (RuntimeController != null)
					RuntimeController.Dispose();

				if (disposing)
				{
					if (ToolWindowControl != null)
					{
						if (((ISynchronizeInvoke) ToolWindowControl).InvokeRequired)
						{
							MethodInvoker invoker = ToolWindowControl.Dispose;
							((ISynchronizeInvoke) ToolWindowControl).Invoke(invoker, null);
						}
						else ToolWindowControl.Dispose();
					}
				}
			}
			catch (Exception e)
			{
				Trace.WriteLine(e);
			}
			IsDisposed = true;
		}

		#endregion

		#region ISerializable Members

		/// <summary>
		/// Populates a <see cref="System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
		/// </summary>
		/// <param name="info">The System.Runtime.Serialization.SerializationInfo to populate with data.</param>
		/// <param name="context">The destination (see System.Runtime.Serialization.StreamingContext) for this serialization.</param>
		[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			if (info != null)
			{
				info.AddValue("luaFilePaths", luaFilePaths);
				info.AddValue("RuntimeShellHost", RuntimeShellHost);
				info.AddValue("WowBenchPath", WowBenchPath);
				info.AddValue("Name", Name);
			}
		}

		#endregion

	}
}